package listboxlib.dialect;


import listboxlib.constant.ListBoxConstant;
import listboxlib.pageutils.ListBoxSQLCreator;
import listboxlib.pageutils.Order;
import listboxlib.pageutils.PageBounds;
import model.dto.ListConfigDTO;
import model.dto.ListQueryDTO;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.SimpleTypeRegistry;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 类似hibernate的Dialect,但只精简出分页部分
 *
 * @author badqiu
 * @author miemiedev
 */
public class Dialect {
    /**
     * 类型处理注册器
     */
    protected TypeHandlerRegistry typeHandlerRegistry;
    /**
     *
     */
    protected MappedStatement mappedStatement;
    /**
     *
     */
    protected PageBounds pageBounds;
    /**
     *
     */
    protected Object parameterObject;
    /**
     *
     */
    protected BoundSql boundSql;
    /**
     *
     */
    protected List<ParameterMapping> parameterMappings;
    /**
     * 主查询参数集合
     */
    protected Map<String, Object> pageParameters = new HashMap<>();
    /**
     * 主查询sql
     */
    private String pageSQL;
    /**
     * 计数查询sql
     */
    private String countSQL;

    private final String SQL_CTRL_LEFT = "select * from (";

    private final String SQL_CTRL_RIGHT = ")";

    private final String param1 = "#param1#";

    private final String param2 = "#param2#";

    private final String parameterKey = "queryDTO";

    Logger logger = LoggerFactory.getLogger(this.getClass());

    public Dialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) {
        this.mappedStatement = mappedStatement;
        this.parameterObject = parameterObject;
        this.pageBounds = pageBounds;
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();

        init();
    }

    protected void init() {

        boundSql = mappedStatement.getBoundSql(parameterObject);
        parameterMappings = new ArrayList(boundSql.getParameterMappings());
        if (parameterObject instanceof Map) {
            pageParameters.putAll((Map) parameterObject);
        } else if (parameterObject != null) {
            Class cls = parameterObject.getClass();
            if (cls.isPrimitive() || cls.isArray() ||
                    SimpleTypeRegistry.isSimpleType(cls) ||
                    Enum.class.isAssignableFrom(cls) ||
                    Collection.class.isAssignableFrom(cls)) {
                for (ParameterMapping parameterMapping : parameterMappings) {
                    pageParameters.put(parameterMapping.getProperty(), parameterObject);
                }
            } else {
                MetaObject metaObject = mappedStatement.getConfiguration().newMetaObject(parameterObject);
                ObjectWrapper wrapper = metaObject.getObjectWrapper();
                for (ParameterMapping parameterMapping : parameterMappings) {
                    PropertyTokenizer prop = new PropertyTokenizer(parameterMapping.getProperty());
                    pageParameters.put(parameterMapping.getProperty(), wrapper.get(prop));
                }
            }

        }
        StringBuffer bufferSql = new StringBuffer(boundSql.getSql().trim());
        if (bufferSql.lastIndexOf(";") == bufferSql.length() - 1) {
            bufferSql.deleteCharAt(bufferSql.length() - 1);
        }
        String sql = bufferSql.toString();
        if (pageParameters.containsKey(parameterKey)) {
            ListQueryDTO listQueryDTO = (ListQueryDTO) pageParameters.get(parameterKey);
            String className = listQueryDTO.getCreatorClass();
            if (!StringUtils.isEmpty(className)) {
                try {
                    Class clazz = Class.forName(className);
                    Constructor constructor = Arrays.stream(clazz.getConstructors()).findFirst().get();
                    ListBoxSQLCreator listBoxSQLCreator = (ListBoxSQLCreator) constructor.newInstance(null);
                    //调用自定义的动态改写列表工具sql方法
                    sql = listBoxSQLCreator.creator(listQueryDTO.getConfigDTOS(), sql);
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                    logger.error("listbox异常：funcId:{},page-holder类实例化异常", listQueryDTO.getFuncId(), e);
                }
            }
        }
        sql = buildSqlParameter(pageParameters, sql);
        pageSQL = sql;
        if (pageBounds.getOrders() != null && !pageBounds.getOrders().isEmpty()) {
            pageSQL = getSortString(sql, pageBounds.getOrders());
        }
        if (pageBounds.getOffset() != RowBounds.NO_ROW_OFFSET
                || pageBounds.getLimit() != RowBounds.NO_ROW_LIMIT) {
            pageSQL = getLimitString(pageSQL, "__offset", pageBounds.getOffset(), "__limit", pageBounds.getLimit());
        }


        countSQL = getCountString(sql);
    }


    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public Object getParameterObject() {
        return pageParameters;
    }


    public String getPageSQL() {
        return pageSQL;
    }

    protected void setPageParameter(String name, Object value, Class type) {
        ParameterMapping parameterMapping = new ParameterMapping.Builder(mappedStatement.getConfiguration(), name, type).build();
        parameterMappings.add(parameterMapping);
        pageParameters.put(name, value);
    }


    public String getCountSQL() {
        return countSQL;
    }


    /**
     * 将sql变成分页sql语句
     */
    protected String getLimitString(String sql, String offsetName, int offset, String limitName, int limit) {
        throw new UnsupportedOperationException("paged queries not supported");
    }

    /**
     * 将sql转换为总记录数SQL
     *
     * @param sql SQL语句
     * @return 总记录数的sql
     */
    protected String getCountString(String sql) {
        return "select count(1) from (" + sql + ") tmp_count";
    }

    /**
     * 将sql转换为带排序的SQL
     *
     * @param sql SQL语句
     * @return 总记录数的sql
     */
    protected String getSortString(String sql, List<Order> orders) {
        if (orders == null || orders.isEmpty()) {
            return sql;
        }

        StringBuffer buffer = new StringBuffer("select * from (").append(sql).append(") temp_order order by ");
        for (Order order : orders) {
            if (order != null) {
                buffer.append(order.toString())
                        .append(", ");
            }

        }
        buffer.delete(buffer.length() - 2, buffer.length());
        return buffer.toString();
    }

    /**
     *根据条件查询参数重新构建sql
     *
     */
    public String buildSqlParameter(Map<String, Object> parameter, String pageSQL) {
        if (!CollectionUtils.isEmpty(parameter) && parameter.containsKey("queryDTO")) {
            ListQueryDTO queryDTO = (ListQueryDTO) parameter.get("queryDTO");
            List<ListConfigDTO> configDTOList = queryDTO.getConfigDTOS();
            if (!CollectionUtils.isEmpty(configDTOList)) {
                StringBuffer pageSqlBuild = new StringBuffer().append(SQL_CTRL_LEFT).append(pageSQL).append(SQL_CTRL_RIGHT).append(" a").append(" where 1=1");
                configDTOList.stream().filter(listConfigDTO -> !ObjectUtils.isEmpty(listConfigDTO.getFieldValue())).forEach(listConfigDTO -> {
                    Integer type = listConfigDTO.getFindType();
                    if (ListBoxConstant.Field_Find_Model.Text_Exact.key.equals(type) || ListBoxConstant.Field_Find_Model.Option_Exact.key.equals(type)) {
                        //精确查询
                        pageSqlBuild.append(" and ").append(listConfigDTO.getTableFieldName()).append(" = ").append(param1);
                    } else if (ListBoxConstant.Field_Find_Model.Text_Fuzzy.key.equals(type) || ListBoxConstant.Field_Find_Model.Option_Fuzzy.key.equals(type)) {
                        //模糊查询
                        pageSqlBuild.append(" and ").append(listConfigDTO.getTableFieldName()).append(" like \'%").append(param1).append("%\'");
                    } else if (ListBoxConstant.Field_Find_Model.Range.key.equals(type)) {
                        //范围查询
                        pageSqlBuild.append(" and ").append(listConfigDTO.getTableFieldName()).append(" >= \'").append(param1).append("\'").append(" and ").append(listConfigDTO.getTableFieldName()).append(" <= \'").append(param2).append("\'");
                    }
                    if (ListBoxConstant.Field_Data_Type.Date.key.equals(listConfigDTO.getType())) {
                        List<String> values = Arrays.asList(listConfigDTO.getFieldValue());
                        if (!CollectionUtils.isEmpty(values)) {
                            int i = pageSqlBuild.indexOf(param1);
                            pageSqlBuild.replace(i, i + param1.length(), values.get(0));
                            int j = pageSqlBuild.indexOf(param2);
                            pageSqlBuild.replace(j, j + param2.length(), values.get(1));
                        }
                    } else {
                        Optional<String> valueOpt = Arrays.asList(listConfigDTO.getFieldValue()).stream().findFirst();
                        if (valueOpt.isPresent()){
                            int i = pageSqlBuild.indexOf(param1);
                            pageSqlBuild.replace(i, i + param1.length(), valueOpt.get());
                        }
                    }
                });
                return pageSqlBuild.toString();
            }
        }
        return pageSQL;
    }

}

